﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Effects.Effects.GuiParts;
using Effects.Effects.Tools;


namespace Effects.Effects
{
 
    /// <summary>
    ///  Chorus effect.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   Chorus is often refered as the king of effctes for electric guitar. As says it's name it tries to simulate another voice playing the same notes, but this second voice is slightly out of tune.
    ///   To produce this we use very similar aproach as in flanger effect. However there are few differences. 
    /// <para>
    ///   The first difference is that the delay time is somewhere about 20 or 30 ms. 
    ///  </para>
    ///  <para>
    ///   The second is that oscillator doesn't control total delay time, but only part of it. To keep it simple, 
    ///   delay time now consist of two parts: min_delay_time and varying_time. LFO controls only length of the varying part. 
    ///   So, actual delay time is min_delay_time + part given by oscillators value.
    ///  </para>
    ///  </para>
     /// </remarks>
    public class Chorus:Effect
    {
        #region controls
        // fields and properties for user controlled parameters 

        #region footswitch

        /// <summary>
        ///  Footswitch for by passing effect.
        /// </summary>
        private Footswitch footswitch;

        /// <summary>
        ///  Led indicating On/Off state.
        /// </summary>
        private LED led;

        /// <summary>
        ///  Synchornizer for footswitch and led.
        /// </summary>
        private FootswitchLedSynchronizer synchronizer;


        /// <summary>
        ///  Key for footswitch settings.
        /// </summary>
        private int footswitchKey = 0;

        #endregion

        #region speed

        /// <summary>
        ///  Pot that controls the period of LFO in delay lin;
        /// </summary>
        protected Pot Speed;

        /// <summary>
        ///  Period od LFO in delay line.
        /// </summary>
        protected int speed;

        /// <summary>
        ///  Key for speed setting.
        /// </summary>
        protected const int speedKey = 1;

        #endregion

        #region depth

        /// <summary>
        ///  Pot that controls dry/wet mix.
        /// </summary>
        protected Pot Depth;

        /// <summary>
        ///  depth - dry/wet mix
        /// </summary>
        protected double depth;

        /// <summary>
        ///  equal to 1 - depth.
        /// </summary>
        /// <remarks> 
        ///  Having this value precomputed saves us lots of operations.
        /// </remarks>
        protected double depth_1;

        /// <summary>
        ///  Key for the depth parameter
        /// </summary>
        protected const int depthKey = 2;

        #endregion

        #region sweep

        /// <summary>
        ///  Constant for min delay time part.
        /// </summary>
        protected const double minDelayTime = 30;

        /// <summary>
        ///  Minimum length of time interval that is controled by LFO.
        /// </summary>
        protected const double varyingInterval_min = 1;
        /// <summary>
        ///  Maximum length of time interval that is controled by LFO.
        /// </summary>
        protected const double varyingInterval_max = 10;


        /// <summary>
        ///  Controls the varying part of delay time.
        /// </summary>
        protected Pot Sweep;

        /// <summary>
        ///  Length of varying time interval
        /// </summary>
        private double varyingInterval;

        /// <summary>
        ///  Key for the sweep parameter
        /// </summary>
        protected const int sweepKey = 3;

        #endregion

        #region oscillator_switching

        /// <summary>
        ///  Switch used to change oscilation shape.
        /// </summary>
        protected Switch oscilatorSwitch;

        /// <summary>
        ///  Oscillating delay line that delays samples.
        /// </summary>
        protected OscillatingDelayLineMinLength buffer;

        /// <summary>
        ///  Current oscillation type: 0 - triangle wave, 1 - sine wave
        /// </summary>
        protected int oscillationType;

        /// <summary>
        ///  Key for oscillation type setting.
        /// </summary>
        protected const int oscillationTypeKey = 4; 
        
        #endregion

        #endregion

        #region effect_properties
        // overridden preperties of base class Effect

        /// <summary>
        ///  Gets or sets sampling rate.
        /// </summary>
        public override uint SampleRate
        {
            set 
            {
                // Change of sample rate causes also change of step and length of buffer
                sampleRate = value;
                buffer = new OscillatingDelayLineMinLength(minDelayTime, varyingInterval_max, (int) sampleRate, buffer.Oscillator);
                buffer.Oscillator.Period = speed;
            }
        }

        /// <summary>
        ///  Gets name of the effect.
        /// </summary>
        public override string Name
        {
            get { return "Chorus 2.0"; }
        }

        #endregion

        #region constructor
        // constructor and comoponents initialization

        /// <summary>
        ///  Creates new instance of Chorus with initial settings. To work properly sample rate must be set afterwards.
        /// </summary>
        public Chorus()
        {
            buffer = new OscillatingDelayLineMinLength(minDelayTime, varyingInterval_max, 44100, new SinOscilator());

            this.BackgroundImage = System.Drawing.Image.FromStream(new System.IO.MemoryStream(global::Effects.Recources.EffectBackgorunds.Chorus));
            this.Size = this.BackgroundImage.Size;
            InitializeComponents();
            InitializeFootswitch();
        }

        /// <summary>
        ///  Initializes all the pots and switch
        /// </summary>
        private void InitializeComponents()
        {
            Speed = new Pot();
            Speed.MaxValue=3000;
            Speed.MinValue=10;
            Speed.ValueChanged += new PotValueChangedEventHandler(this.SpeedPotChanged);
            Speed.Location = new System.Drawing.Point(5, 38);
            Speed.Knob = PotKnob.ChickenKnobMiddle;
            //Speed.Size = new System.Drawing.Size(110, 110);

            Depth = new Pot();
            Depth.MaxValue = 1;
            Depth.MinValue = 0;
            Depth.ValueChanged += new PotValueChangedEventHandler(this.DepthPotChanged);
            Depth.Location = new System.Drawing.Point(220, 38);
            Depth.Knob = PotKnob.ChickenKnobMiddle;
            //Depth.Size = new System.Drawing.Size(110, 110);

            Sweep = new Pot();
            Sweep.MaxValue = varyingInterval_max;
            Sweep.MinValue = varyingInterval_min;
            Sweep.ValueChanged += new PotValueChangedEventHandler(this.SweepPotChanged);
            Sweep.Location = new System.Drawing.Point(110, 38);
            Sweep.Knob = PotKnob.ChickenKnobMiddle;

            oscilatorSwitch = new Switch(2);
            oscilatorSwitch.Location = new System.Drawing.Point(83, 120);
            oscilatorSwitch.Height = 20;
            oscilatorSwitch.Width = 20;
            oscilatorSwitch.OnSwitch += new SwitchSwitched(this.OscilatorSwitchSwitched);
            oscilatorSwitch.SwitchKnobColor = System.Drawing.Color.FromArgb(255,240,5);


            this.Controls.Add(Speed);
            this.Controls.Add(Depth);
            this.Controls.Add(Sweep);
            this.Controls.Add(oscilatorSwitch);
            Speed.Reset();
            Depth.Reset();
            Sweep.Reset();
        }

        /// <summary>
        ///  Iniializes footswitch and led.
        /// </summary>
        private void InitializeFootswitch()
        {
            // footswitch
            footswitch = new Footswitch();
            switchedOn = true;
            footswitch.On = true;
            footswitch.Switched += new FootswitchSwitchedEventHandler(this.FootswitchSwitched);
            footswitch.Location = new System.Drawing.Point(185, 110);
            Controls.Add(footswitch);

            // led
            led = new LED();
            led.On = true;
            led.Location = new System.Drawing.Point(190, 40);
            Controls.Add(led);

            // synchronization
            synchronizer = new FootswitchLedSynchronizer(footswitch, led);
        }

        #endregion

        #region main_work
        // main method of the Delay effect

        /// <summary>
        ///  Effects given input signal.
        /// </summary>
        /// <param name="data"> Input signal. </param>
        /// <returns> Ouput signal of the effect </returns>
        public override double[] effect(double[] data)
        {
            if (switchedOn)
            {
                // process given data
                double[] outData = new double[data.Length];
                for (int i = 0; i < data.Length; i++)
                {
                    outData[i] = depth_1 * data[i] + depth * buffer.Step(data[i]);

                }
                return outData;
            }
            else
            {
                // effect is bypassed, just pass data to buffer and return dry input
                for (int i = 0; i < data.Length; i++)
                    buffer.Step(data[i]);

                return data;
            }
        }

        #endregion

        #region controls_event_handlers
        // handling evetns from controls

        /// <summary>
        ///  Speed pot event handler 
        /// </summary>
        /// <param name="sender"> Object that has raised the event == Speed pot </param>
        /// <param name="e"> Event arguments </param>
        protected void SpeedPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.speed = e.NewValueInt;
            this.buffer.Oscillator.Period = speed;

            EffectSettingsChangedEventArgs args = new EffectSettingsChangedEventArgs(speedKey, speed);
            if (SettingsChanged != null)
                SettingsChanged(this, args);
        }

        /// <summary>
        ///  Depth pot event handler
        /// </summary>
        /// <param name="sender"> Object that has raised the event == Depth pot </param>
        /// <param name="e"> Event arguments </param>
        protected void DepthPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.depth = e.NewValue;
            depth_1 = 1 - depth;

            EffectSettingsChangedEventArgs args = new EffectSettingsChangedEventArgs(depthKey, depth);
            if (SettingsChanged != null)
                SettingsChanged(this, args);
        }

        /// <summary>
        ///  Sweep pot event handler
        /// </summary>
        /// <param name="sender"> Object that has raised the event == Sweep pot </param>
        /// <param name="e"> Event arguments </param>
        protected void SweepPotChanged(object sender, PotValueChangedEventArgs e)
        {
            this.varyingInterval = e.NewValueInt;
            buffer.VaryingTime = varyingInterval;

            EffectSettingsChangedEventArgs args = new EffectSettingsChangedEventArgs(sweepKey, varyingInterval);
            if (SettingsChanged != null)
                SettingsChanged(this, args);
        }

        /// <summary>
        ///  Sets oscillator to buffer according to the given state.
        /// </summary>
        /// <param name="state"> Oscillation type: 0 - triangle, 1 - sin </param>
        protected void SetOscillator(int state)
        {
            Oscillator newOscilator = null;
            // create new oscillator according to the state
            switch (state)
            {

                case 0:
                    // triangle oscilator
                    newOscilator = new TriangleOscilator();
                    break;
                case 1:
                    //sin oscilator
                    newOscilator = new SinOscilator();
                    break;
                case 2:
                    break;
            }

            // period stays the same
            newOscilator.Period = buffer.Oscillator.Period;

            // set oscillator to the buffer
            buffer.Oscillator = newOscilator;
            oscillationType = state;
        }

        /// <summary>
        ///  Oscilator switch event handler
        /// </summary>
        /// <param name="sender"> Switch that has raised the event == oscillatorSwitch </param>
        /// <param name="e"> Event argumetns </param>
        protected void OscilatorSwitchSwitched(object sender, SwitchEventArgs e)
        {
            SetOscillator(e.State);

            EffectSettingsChangedEventArgs args = new EffectSettingsChangedEventArgs(oscillationTypeKey, e.State);
            if (SettingsChanged != null)
                SettingsChanged(this, args);
        }

        /// <summary>
        ///  Footswitch.Switched event handler. According to the event arguments signal bypasses or runs through the effect.
        /// </summary>
        /// <param name="sender"> Object that has raised the event. == Footswitch </param>
        /// <param name="args"> Event Arguments </param>
        private void FootswitchSwitched(object sender, FootswitchSwitchedEventArgs args)
        {
            this.switchedOn = args.On;

            // raise the event
            if (this.SettingsChanged != null)
            {
                EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(footswitchKey, switchedOn);
                this.SettingsChanged(this, changeArgs);
            }
        }

        #endregion

        #region settings
        // getting and setting the settings of Tremolo

        /// <summary>
        ///  Raised when any parameter of effect is changed.
        /// </summary>
        public override event EffectSettingsChangedDelegate SettingsChanged;

        /// <summary>
        ///  Gets all current settings of the effect.
        /// </summary>
        /// <returns> All current seetings </returns>
        public override object[] GetAllSettings()
        {
            object[] settings = new object[5];
            settings[0] = switchedOn;
            settings[1] = depth;
            settings[2] = speed;
            settings[3] = varyingInterval;
            settings[4] = oscillationType;

            return settings;
        }


        /// <summary>
        ///  Sets all settings.
        /// </summary>
        /// <param name="settings"> All settings of the effect. This argument should be output of GetAllSettings method that has been called previously. </param>
        public override void SetAllSettings(object[] settings)
        {
            // cast settings to object array
            object[] settingsArray = (object[])settings;

            // go throuhgt the all settings and set them
            this.depth = (double)settingsArray[1];
            depth_1 = 1 - depth;
            Depth.SetValue(depth);

            this.speed = (int)settingsArray[2];
            buffer.Oscillator.Period = speed;
            Speed.SetValue(speed);

            this.varyingInterval = (double)settingsArray[3];
            buffer.VaryingTime = varyingInterval;
            Sweep.SetValue(varyingInterval);


            SetOscillator((int)settingsArray[4]);
            oscilatorSwitch.State = oscillationType;
        }

         /// <summary>
        ///  Sets one settings
        /// </summary>
        /// <param name="e"> Setting change to be done </param>
        public override void SetSetting(EffectSettingsChangedEventArgs e)
        {
            int key = (int)e.Key;

            if (key == depthKey)
            {
                this.depth = (double)e.Value;
                depth_1 = 1 - depth;
                Depth.SetValue(depth);
                return;
            }

            if (key == speedKey)
            {
                this.speed = (int)e.Value;
                buffer.Oscillator.Period = speed;
                Speed.SetValue(speed);

                return;
            }

            if (key == sweepKey)
            {
                varyingInterval = (double)e.Value;
                buffer.VaryingTime = varyingInterval;
                Sweep.SetValue(varyingInterval);
                return;
            }

            if (key == oscillationTypeKey)
            {
                SetOscillator((int)e.Value);
                oscilatorSwitch.State = oscillationType;
                return;
            }

            if (key == footswitchKey)
            {
                switchedOn = (bool)e.Value;
                synchronizer.SetValue(switchedOn);
                return;
            }
        }

        /// <summary>
        ///  Return array desribing all parameters.
        /// </summary>
        /// <returns> Array describing parameters </returns>
        public override EffectParam[] GetParameters()
        {
            EffectParam[] parameters = new EffectParam[5];
            ListItem[] footswitchState = new ListItem[2];
            footswitchState[0] = new ListItem("Turn on", true);
            footswitchState[1] = new ListItem("Turn off", false);
            parameters[0] = new EffectParam(footswitchKey, "ON/OFF", footswitchState);


            parameters[1] = new EffectParam(depthKey, "Depth", 0F, 1F);
            parameters[2] = new EffectParam(speedKey, "Speed", 10, 3000);
            parameters[3] = new EffectParam(sweepKey, "Sweep", 1F, 10F);

            ListItem[] oscillationTypes = new ListItem[2];
            oscillationTypes[0] = new ListItem("Trianlge wave",0);
            oscillationTypes[1] = new ListItem("Sin wave",1);
            parameters[4] = new EffectParam(oscillationTypeKey, "Oscillation type", oscillationTypes);

            return parameters;
        }

        #endregion
    } 
}
